// 参考： https://threejsfundamentals.org/threejs/lessons/threejs-post-processing.html
import { 
  Mesh,
  PerspectiveCamera,
  Scene, 
  WebGLRenderer,
  TextureLoader,
  MeshPhongMaterial,
  Color,
  Vector2,
  SphereGeometry,
  Object3D,
  Vector3,
  SpotLight,
  AmbientLight,
} from "three";
import stats from '../utils/stats';
// 调用
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { GUI } from "dat.gui";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass";
import { CopyShader } from "three/examples/jsm/shaders/CopyShader";
/** 该着色器可以创建出漂白的效果。通过该效果可以使图片看上去像覆盖了一层银 */
import { BleachBypassShader } from 'three/examples/jsm/shaders/BleachBypassShader';
// 该着色器可以改变图片的亮度与对比度
import { BrightnessContrastShader } from 'three/examples/jsm/shaders/BrightnessContrastShader';
// 该着色器可以将颜色覆盖在整个屏幕上
import { ColorifyShader } from "three/examples/jsm/shaders/ColorifyShader";
// 该着色器可以调整颜色的分布
import { ColorCorrectionShader } from 'three/examples/jsm/shaders/ColorCorrectionShader';
// 该着色器提供图像边缘检测功能
import { FreiChenShader } from 'three/examples/jsm/shaders/FreiChenShader';
// 该着色器使用固定 Gamma 因数2对渲染画面进行 Gamma 校正。除了该着色器之外，也可以通过调节WebGLRenderer的 gammaFactor、gammaInput
// 和 gammaOutout 三个属性来实现 Gamma 校正
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader';
// 改变颜色的色调和饱和度
import { HueSaturationShader } from 'three/examples/jsm/shaders/HueSaturationShader';
// 万花筒效果，该效果会围绕场景的中央呈径向反射
import { KaleidoShader } from 'three/examples/jsm/shaders/KaleidoShader';
// 提供亮度效果，可以显示场景的亮度
import { LuminosityHighPassShader } from 'three/examples/jsm/shaders/LuminosityHighPassShader';
// 提供亮度效果，可以显示场景的亮度
import { LuminosityShader } from 'three/examples/jsm/shaders/LuminosityShader';
// 创建镜面效果
import { MirrorShader } from 'three/examples/jsm/shaders/MirrorShader';
// 该着色器可以为画面创建像素化效果
import { PixelShader } from 'three/examples/jsm/shaders/PixelShader';
// 将红绿蓝三种颜色分开
import { RGBShiftShader } from 'three/examples/jsm/shaders/RGBShiftShader';
// 创建类似于乌贼墨的效果
import { SepiaShader } from 'three/examples/jsm/shaders/SepiaShader';
// 提供图像边缘检测功能着色器
import { SobelOperatorShader } from 'three/examples/jsm/shaders/SobelOperatorShader';
// 添加晕映效果。该效果可以在图片的中央周围显示暗边框
import { VignetteShader } from 'three/examples/jsm/shaders/VignetteShader';

let textureLoader = new TextureLoader()
let renderer = setRenderer();
renderer.autoClear = false;
let camera = setCamera();
let scene = new Scene();
scene.background = textureLoader.load('/textures/bg/starry-deep-outer-space-galaxy.jpg')

var earthAndLight = addEarth(scene);


let renderPass = new RenderPass(scene, camera);
let effectCopy = new ShaderPass(CopyShader);
effectCopy.renderToScreen = true;
// 创造一种镀银的效果
let bleachByPassFilter = new ShaderPass(BleachBypassShader);
// 改变亮度和对比度
let brightnessContrastShader = new ShaderPass(BrightnessContrastShader);
// 将某种颜色覆盖到整个屏幕
let colorifyShader = new ShaderPass(ColorifyShader);
// 调整颜色的分布
let colorCorrectionShader = new ShaderPass(ColorCorrectionShader);
let freiChenShader = new ShaderPass(FreiChenShader);
let gammaCorrectionShader = new ShaderPass(GammaCorrectionShader);
let hueSaturationShader = new ShaderPass(HueSaturationShader);
let kaleidoShader = new ShaderPass(KaleidoShader);
let luminosityHighPassShader = new ShaderPass(LuminosityHighPassShader);
let luminosityShader = new ShaderPass(LuminosityShader);
let mirrorShader = new ShaderPass(MirrorShader);
let pixelShader = new ShaderPass(PixelShader);
pixelShader.uniforms.resolution.value = new Vector2(256, 256);
let rgbShiftShader = new ShaderPass(RGBShiftShader);
let sepiaShader = new ShaderPass(SepiaShader);
let sobelOperatorShader = new ShaderPass(SobelOperatorShader);
sobelOperatorShader.uniforms.resolution.value = new Vector2(256, 256);
let vignetteShader = new ShaderPass(VignetteShader);

let composer = new EffectComposer(renderer);
composer.addPass(renderPass);
composer.addPass(bleachByPassFilter);
composer.addPass(brightnessContrastShader);
composer.addPass(colorifyShader);
composer.addPass(colorCorrectionShader);
composer.addPass(freiChenShader);
composer.addPass(gammaCorrectionShader);
composer.addPass(hueSaturationShader);
composer.addPass(kaleidoShader);
composer.addPass(luminosityHighPassShader);
composer.addPass(luminosityShader);
composer.addPass(mirrorShader);
composer.addPass(pixelShader);
composer.addPass(rgbShiftShader);
composer.addPass(sepiaShader);
composer.addPass(sobelOperatorShader);
composer.addPass(vignetteShader);
composer.addPass(effectCopy);

const trackballControls = new OrbitControls(camera, renderer.domElement);

let gui = new GUI();

addShaderControl(gui, "BleachBypass", bleachByPassFilter, { floats: [{ key: "opacity", from: 0, to: 2, step: 0.01 }]});
addShaderControl(
  gui, "亮度对比(BrightnessContrast)", brightnessContrastShader, 
  { floats: [ 
    { key: "brightness", name: '亮度', from: 0, to: 1, step: 0.01 }, 
    { key: "contrast", name:'对比度', from: 0, to: 1, step: 0.01 } 
  ] }
);
addShaderControl(gui, "着色(Colorify)", colorifyShader, { colors: [ {key: "color"}] });
addShaderControl(gui, "调整颜色的分布(ColorCorrection)", colorCorrectionShader, {
  vector3: [
    { key: "powRGB", from: {x: 0, y: 0, z:0}, to: {x:5, y:5, z:5}, step: {x:0.01, y:0.01, z:0.01}},
    { key: "mulRGB", from: {x: 0, y: 0, z:0}, to: {x:5, y:5, z:5}, step: {x:0.01, y:0.01, z:0.01}},
    { key: "addRGB", from: {x: 0, y: 0, z:0}, to: {x:1, y:1, z:1}, step: {x:0.01, y:0.01, z:0.01}}
  ]
});
addShaderControl(gui, "❌ FreiChen", freiChenShader, { vector2: [{ key: "aspect", from: {x: 128, y:128}, to: {x: 1024, y: 1024}, step:{x:1, y:1}}]})
addShaderControl(gui, "伽瑪校正(GammaCorrection)", gammaCorrectionShader, {});
addShaderControl(gui, "色调和饱和度(HueSaturation)", hueSaturationShader, { 
  floats: [
    { key: "hue", name: '色相', from: -1, to: 1, step: 0.01 }, 
    { key: "saturation", name: '饱和度', from: -1, to: 1, step: 0.01 }]
});
addShaderControl(gui, "万花筒(Kaleido)", kaleidoShader, { 
  floats: [
    { key: "sides", from: 0, to: 20, step: 1 }, 
    { key: "angle", from: 0, to: 6.28, step: 0.01 }
  ]
});
addShaderControl(gui, "LuminosityHighPass", luminosityHighPassShader, {
  colors: [ {key: "defaultColor"}], 
  floats: [
    { key: "luminosityThreshold", from: 0, to: 2, step: 0.01 }, 
    { key: "smoothWidth", from: 0, to: 2, step: 0.01 },  
    { key: "defaultOpacity", from: 0, to: 1, step: 0.01 }
  ]
});
addShaderControl(gui, "Luminosity", luminosityShader, {});
addShaderControl(gui, "Mirror", mirrorShader, { floats: [{ key: "side", from: 0, to: 3, step: 1 }]})
addShaderControl(gui, "Pixel", pixelShader, { vector2: [{ key: "resolution", from: {x: 2, y:2}, to: {x: 512, y: 512}, step:{x:1, y:1}}], floats: [{ key: "pixelSize", from: 0, to: 10, step: 1 }]})
addShaderControl(gui, "rgbShift", rgbShiftShader, { floats: [{key: "angle", from: 0, to: 6.28, step: 0.001}, { key: "amount", from: 0, to: 0.5, step: 0.001 }]})
addShaderControl(gui, "sepia", sepiaShader, { floats: [{ key: "amount", from: 0, to: 10, step: 0.01 }]})
addShaderControl(gui, "sobelOperator", sobelOperatorShader, {  vector2: [{ key: "resolution", from: {x: 2, y:2}, to: {x: 512, y: 512}, step:{x:1, y:1}}]})
addShaderControl(gui, "vignette", vignetteShader, { floats: [{ key: "offset", from: 0, to: 10, step: 0.01 }, { key: "darkness", from: 0, to: 10, step: 0.01 }]})

render();
function render() {
  stats.update();
  trackballControls.update();

  earthAndLight.earth.rotation.x += 0.0001;
  earthAndLight.earth.rotation.y += 0.001;

  requestAnimationFrame(render);
  composer.render();
}


function setRenderer() {
  const renderer = new WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 支持阴影生成 
  renderer.shadowMap.enabled = true;
  document.body.appendChild(renderer.domElement);

  return renderer
}

function setCamera(): PerspectiveCamera {
  let camera = new PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
  camera.position.set(0, 20, 40);

  return camera;
}


function addEarth(scene: Scene) {
  let textureLoader = new TextureLoader();
  let planetMaterial = new MeshPhongMaterial({
    map: textureLoader.load("/textures/earth/Earth.png"),
    normalMap: textureLoader.load("/textures/earth/EarthNormal.png"),
    specularMap: textureLoader.load("/textures/earth/EarthSpec.png"),
    specular: new Color(0x4444aa),
    normalScale: new Vector2(6,6),
    shininess: 0.5
  });
  
  let earth = new Mesh(new SphereGeometry(15, 40, 40), planetMaterial);
  scene.add(earth);
  let pivot = new Object3D();
  initDefaultLighting(pivot);
  scene.add(pivot);

  return {earth: earth, pivot: pivot};
}

function initDefaultLighting(scene: any, initialPosition?: any) {
  let position = (initialPosition !== undefined) ? initialPosition : new Vector3(-10, 30, 40);
  
  let spotLight = new SpotLight(0xffffff);
  spotLight.position.copy(position);
  spotLight.shadow.mapSize.width = 2048;
  spotLight.shadow.mapSize.height = 2048;
  spotLight.shadow.camera.fov = 15;
  spotLight.castShadow = true;
  spotLight.decay = 2;
  spotLight.penumbra = 0.05;
  spotLight.name = "spotLight"

  scene.add(spotLight);

  let ambientLight = new AmbientLight(0x343434);
  ambientLight.name = "ambientLight";
  scene.add(ambientLight);  
}

interface ITF_controls {
  [name: string]: any
}
function addShaderControl(gui: GUI, folderName: string, shaderPass: any, toSet: any, enabled?: any) {
  function uniformOrDefault(uniforms: any, key: string, def: any) {
    return (uniforms[key].value !== undefined && uniforms[key].value !== null ) ? uniforms[key].value : def;
  }

  function addUniformBool(folder: GUI, key: string, shader: any) {
    let localControls: ITF_controls = {};
    localControls[key] = uniformOrDefault(shader.uniforms, key, 0);
    folder.add(localControls, key).onChange(function(v) {shader.uniforms[key].value = v}); 
  }

  function addUniformFloat(folder: GUI, key: string, from: any, to: any, step: any, shader: any) {
    let localControls: ITF_controls = {};
    localControls[key] = uniformOrDefault(shader.uniforms, key, 0);
    folder.add(localControls, key, from, to, step).onChange(function(v) {shader.uniforms[key].value = v});
  }

  function addUniformColor(folder: GUI, key: string, shader: any) {
    let localControls: ITF_controls = {}
    localControls[key] = uniformOrDefault(shader.uniforms, key, new Color(0xffffff));
    folder.addColor( localControls, key ).onChange( function ( value ) {  
      shader.uniforms[key].value = new Color().setRGB(value.r / 255, value.g / 255, value.b / 255);
      }
    );
  }

  function addUniformVector3(folder: GUI, key: string, shader: any, from: any, to: any, step: any) {
    let startValue = uniformOrDefault(shader.uniforms, key, new Vector3(0, 0, 0));
    let keyX = key + "_x";
    let keyY = key + "_y";
    let keyZ = key + "_z";

    let localControls: ITF_controls = {};
    localControls[keyX] = startValue.x;
    localControls[keyY] = startValue.y;
    localControls[keyZ] = startValue.z;

    folder.add(localControls, keyX, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.x = v});
    folder.add(localControls, keyY, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.y = v});
    folder.add(localControls, keyZ, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.z = v});
  }

  function addUniformVector2(folder: GUI, key: string, shader: any, from: any, to: any, step: any) {
    let startValue = uniformOrDefault(shader.uniforms, key, new Vector2(0, 0));
    shader.uniforms[key].value = startValue;

    let keyX = key + "_x";
    let keyY = key + "_y";

    let localControls: ITF_controls = {};
    localControls[keyX] = startValue.x;
    localControls[keyY] = startValue.y;

    folder.add(localControls, keyX, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.x = v});
    folder.add(localControls, keyY, from.x, to.x, step.x).onChange(function(v) {shader.uniforms[key].value.y = v});
  }

  // create the folder and set enabled
  let folder = gui.addFolder(folderName);
  if (toSet.setEnabled !== undefined ? toSet.setEnabled : true) {
    shaderPass.enabled = enabled !== undefined ? enabled : false;
    folder.add(shaderPass, "enabled");
  }

  if (toSet.floats !== undefined) {
    toSet.floats.forEach(function (p: any) {
      let from = p.from !== undefined ? p.from : 0;
      let to = p.from !== undefined ? p.to : 1;
      let step = p.from !== undefined ? p.step : 0.01;
      addUniformFloat(folder, p.key, from, to, step, shaderPass)
    });
  }

  if (toSet.colors !== undefined) {
    toSet.colors.forEach(function (p: any) {
      addUniformColor(folder, p.key, shaderPass)
    });
  }

  if (toSet.vector3 !== undefined) {
    toSet.vector3.forEach(function (p: any) {
      addUniformVector3(folder, p.key, shaderPass, p.from, p.to, p.step)
    });
  }

  if (toSet.vector2 !== undefined) {
    toSet.vector2.forEach(function (p: any) {
      addUniformVector2(folder, p.key, shaderPass, p.from, p.to, p.step)
    });
  }

  if (toSet.booleans !== undefined) {
    toSet.booleans.forEach(function(p: any) {
      addUniformBool(folder, p.key, shaderPass)
    })
  }
}
  
